﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Unity;

using SharedGenomics.Core;

namespace SharedGenomics.Workbench.Core
{
    public class PresenterFactory
    {
        private IDictionary<Type, IPresenter> _cachedPresenters;
        private LinkedList<IPresenterInitialisationStage> _initialiationStages;

        public PresenterFactory(IUnityContainer container)
        {
            this.Container = container;
            this._cachedPresenters = new Dictionary<Type, IPresenter>();
            this._initialiationStages = new LinkedList<IPresenterInitialisationStage>();
        }

        public virtual IPresenter Get<T>() where T : IPresenter
        {
            return this.Get(typeof(T), false);
        }

        public virtual IPresenter Get<T>(bool cache) where T : IPresenter
        {
            return this.Get(typeof(T), cache);
        }

        public virtual IPresenter Get(Type presenterType)
        {
            return this.Get(presenterType, false);
        }

        public virtual IPresenter Get(Type presenterType, bool cache)
        {
            //return presenter;
            return this.Get(presenterType, cache, null, null);
        }

        public virtual IPresenter Get(bool cache, IPresenter existing)
        {
            existing.ThrowIfNull("Cannot build up null presenter");
            return this.Get(existing.GetType(), cache, null, existing);
        }

        public virtual IPresenter Get<T>(bool cache, Action<T> initialisationAction) where T : IPresenter
        {
            return this.Get(typeof(T), cache, initialisationAction, null);
        }

        public virtual IPresenter Get(Type presenterType, bool cache, Delegate initialisationAction, IPresenter existing)
        {
            PresenterInitialisationContext context = new PresenterInitialisationContext()
            {
                PresenterType = presenterType,
                AddToCache = cache,
                InitialisationAction = initialisationAction,
                Current = existing
            };

            return this.Get(context);
        }

        public virtual IPresenter Get(IPresenterInitialisationContext context)
        {
            context.ThrowIfNull("context");
            ExceptionHelper.ThrowIf<InvalidOperationException>(this._initialiationStages.Count == 0, "Cannot create presenter - no initialisation stages have been added");

            context.Container = this.Container;
            var current = this._initialiationStages.First;
            LinkedListNode<IPresenterInitialisationStage> next = null;

            //call initialise on the first stage - all further calls will be on the 'next' node - this means that current will track the last stage to complete once the next pointer has 
            //been updated
            bool continueInit = current.Value.Initialise(context);
            next = current.Next;

            for (; next != null && continueInit; current = next, next = next.Next)
            {
                continueInit = next.Value.Initialise(context);
            }

            //at this point current contains the last item in the chain to be executed (or null if the list of stages is empty)
            //go back through the chain and call the post initialise method on each stage
            for (; current != null; current = current.Previous)
            {
                current.Value.PostInitialise(context);
            }

            context.Current.ThrowIfNull<ArgumentException>("Unable to create presenter");
            return context.Current;
        }

        public void AddCreationStage(IPresenterInitialisationStage stage)
        {
            stage.ThrowIfNull("stage");
            this._initialiationStages.AddLast(stage);
        }

        public void AddCreationStage<TStage>() where TStage : IPresenterInitialisationStage, new()
        {
            this.AddCreationStage(new TStage());
        }

        private IUnityContainer Container { get; set; }
    }
}
